/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Text;
using MyPhotoIndex.ImageCache;
using System.IO;
using MyPhotoIndex.Utilities;
using MyPhotoIndex.DataAccess;
using System.Drawing;
using MyPhotoIndex.Interfaces;
using System.Threading;
using MyPhotoIndex.Interfaces.Imaging;

namespace MyPhotoIndex.ImageBank
{
    public class ApplicativeImageCache : IDisposable
    {
        public delegate void ProgressDelegate(int maximum, int value);

        private ImageCacheInstance m_ifc;
        private List<IImageData> m_imageSortedList;
        private int m_lastUsedImageId;
        private int m_lastUsedImageIdReferance;
        private Thread m_cachingThread;
        private bool m_disposed;
        private bool m_running;
        private Dictionary<int, byte[]> m_imageCahce = new Dictionary<int, byte[]>();
        private int m_cahceSize = 50;
        private Object m_syncLock = new object();

        public void Load(List<IImageData> imageSortedList)
        {
            if (m_ifc != null)
            {
                m_ifc.Dispose();
                m_ifc = null;
            }

            String imageCacheFile = ApplicationFolders.CachePath;
            try
            {
                if (Directory.Exists(imageCacheFile) == false)
                {
                    Directory.CreateDirectory(imageCacheFile);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }

            String indexId = DataAccessProvider.Instance.GetDbIndexId();
            imageCacheFile = Path.Combine(imageCacheFile, indexId);
            imageCacheFile = Path.ChangeExtension(imageCacheFile, "icf");

            m_ifc = new ImageCacheInstance(imageCacheFile);
            m_imageSortedList = imageSortedList;

            if (m_cachingThread != null)
            {
                try
                {
                    m_running = false;
                    m_cachingThread.Join(500);
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }
            }

            lock (m_syncLock)
            {
                m_imageCahce.Clear();
            }

            m_running = true;
            m_cachingThread = new Thread(new ThreadStart(CachingThread));
            m_cachingThread.Start();
        }

        internal void CachingThread()
        {
            while (m_disposed == false &&
                   m_running == true)
            {
                try
                {
                    if (m_lastUsedImageIdReferance != m_lastUsedImageId)
                    {
                        int cacheSize = Math.Min(m_imageSortedList.Count, m_cahceSize);
                        IImageData[] imageDataArray = m_imageSortedList.ToArray();
                        int[] cahcheArray = new int[cacheSize];
                        for (int i = 0; i < imageDataArray.Length; i++)
                        {
                            if (imageDataArray[i].Id == m_lastUsedImageId)
                            {
                                i = Math.Max(0, i - 20);
                                for (int j = 0; j < cacheSize && i < imageDataArray.Length; j++)
                                {
                                    cahcheArray[j] = imageDataArray[i++].Id;
                                }
                                break;
                            }
                        }

                        Dictionary<int, byte[]> imageCahce = new Dictionary<int, byte[]>();
                        foreach (int imageId in cahcheArray)
                        {
                            byte[] imageBytes = null;
                            if (m_imageCahce.TryGetValue(imageId, out imageBytes) == false)
                            {
                                try
                                {
                                    imageBytes = m_ifc.Find(imageId);
                                }
                                catch (Exception ex)
                                {
                                    Logger.Log(ex);
                                }
                            }

                            if (imageId == 0)
                            {
                                break;
                            }

                            if (null != imageBytes)
                            {
                                imageCahce.Add(imageId, imageBytes);
                            }
                        }

                        lock (m_syncLock)
                        {
                            m_imageCahce.Clear();
                            m_imageCahce = imageCahce;
                        }
                    }

                    m_lastUsedImageIdReferance = m_lastUsedImageId;
                    Thread.Sleep(100);
                }
                catch (Exception ex)
                {
                    Logger.Log(ex);
                }
            }
        }

        public void Add(int id, Image image)
        {
            try
            {
                m_ifc.Add(id, ThumbnailUtilities.ThumbnailToByteArray(image));
                if (m_imageCahce.ContainsKey(id) == true)
                {
                    m_imageCahce.Remove(id);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        public void Remove(int id)
        {
            try
            {
                m_ifc.Delete(id);
                m_imageCahce.Remove(id);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        public Image Find(int imageId, Size requestedSize)
        {
            if (m_ifc == null)
            {
                return null;
            }

            Image image = null;
            byte[] imageData = null;
            lock (m_syncLock)
            {
                m_imageCahce.TryGetValue(imageId, out imageData);
            }

            m_lastUsedImageId = imageId;

            if (imageData == null)
            {
                try
                {
                    imageData = m_ifc.Find(imageId);
                }
                catch (IOException ex)
                {
                    Logger.Log(ex);
                    string path = m_ifc.Path;
                    m_ifc = new ImageCacheInstance(path);
                    imageData = m_ifc.Find(imageId);
                }

                if (imageData == null)
                {
                    return null;
                }
            }

            image = ThumbnailUtilities.ThumbnailFromByteArray(imageData);
            if (requestedSize == Size.Empty)
            {
                return image;
            }

            if (image == null)
            {
                return null;
            }

            using (image)
            {
                if (ImageAlgo.IsLandscapeImage(image.Size))
                {
                    if (image.Size.Width < requestedSize.Width - 5)
                    {
                        return null;
                    }
                }
                else
                {
                    if (image.Size.Height < requestedSize.Height - 5)
                    {
                        return null;
                    }
                }

                return new Bitmap(image, requestedSize);
            }
        }

        internal void Reindex(ProgressDelegate progressDelegate)
        {
            m_cachingThread.Suspend();

            m_ifc.Reindex(new ImageCacheInstance.ProgressDelegate(ReindexProgress), progressDelegate);

            m_cachingThread.Resume();
        }

        internal void ReindexProgress(int maximum, int value, object tag)
        {
            ProgressDelegate progressDelegate = tag as ProgressDelegate;
            if (progressDelegate != null)
            {
                progressDelegate.Invoke(maximum, value);
            }
        }

        internal long GetPhysicalSizeBytes()
        {
            return m_ifc.GetPhysicalSizeBytes();
        }

        internal long GetFreeSpaceBytes()
        {
            return m_ifc.GetFreeSpaceBytes();
        }

        internal void Clear()
        {
            lock (m_syncLock)
            {
                m_imageCahce.Clear();
            }
        }

        internal void UpgradeImageCache(ProgressDelegate progressDelegate)
        {
            try
            {
                String imageCacheFile = ApplicationFolders.CachePath;

                if (Directory.Exists(imageCacheFile) == false)
                {
                    return;
                }

                String indexId = DataAccessProvider.Instance.GetDbIndexId();
                imageCacheFile = Path.Combine(imageCacheFile, indexId);
                imageCacheFile = Path.ChangeExtension(imageCacheFile, "icf");

                ImageCacheInstance.UpgradeCacheFile(imageCacheFile, new ImageCacheInstance.ProgressDelegate(ReindexProgress), progressDelegate);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        internal bool NeedUpgrade()
        {
            String imageCacheFile = ApplicationFolders.CachePath;

            if (Directory.Exists(imageCacheFile) == false)
            {
                return false;
            }

            String indexId = DataAccessProvider.Instance.GetDbIndexId();
            imageCacheFile = Path.Combine(imageCacheFile, indexId);
            imageCacheFile = Path.ChangeExtension(imageCacheFile, "icf");

            if (File.Exists(imageCacheFile) == false)
            {
                return false;
            }

            try
            {
                return (ImageCacheInstance.GetCahceMaxSize(imageCacheFile) < ImageCacheInstance.DEFAULT_MAX_IMAGES);
            }
            catch (Exception)
            {
                return false;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            m_disposed = true;
            m_running = false;
            if (m_cachingThread != null)
            {
                m_cachingThread.Join(500);
            }

            if (m_ifc != null)
            {
                m_ifc.Dispose();
            }
        }

        #endregion
    }
}
